// Copyright (c) 2022 Presto Labs Pte. Ltd.
// Author: fengyang
#pragma once

#include <iostream>
#include <memory>
#include <optional>
#include <regex>
#include <set>
#include <sstream>
#include <string>
#include <vector>

#include <boost/date_time/posix_time/posix_time.hpp>

#include "coin2/exchange/base/symbology/derivative_util.h"
#include "coin2/exchange/base/symbology/futures_product.h"
#include "coin2/exchange/bitget_futures/symbology/currency.h"

namespace coin2::exchange::bitget_futures::symbology {
using ExchangeType = base::market::ExchangeType;
using coin2::exchange::bitget_futures::symbology::BitgetFuturesCurrency;
class BitgetFuturesProduct
    : public base::symbology::FuturesProduct<BitgetFuturesCurrency, BitgetFuturesProduct>,
      public base::symbology::ProductFromStrMixin<BitgetFuturesProduct> {
 public:
  using FuturesProduct =
      base::symbology::FuturesProduct<BitgetFuturesCurrency, BitgetFuturesProduct>;
  using ContractType = base::symbology::ContractType;

 public:
  BitgetFuturesProduct(
      const BitgetFuturesCurrency& base,
      const BitgetFuturesCurrency& quote,
      const std::string& native_product,
      const std::string& native_subscription_symbol,
      const ContractType contract_type,
      const int64_t expiry_timestamp,
      const int64_t resolve_timestamp)
      : FuturesProduct(
            base,
            quote,
            native_product,
            native_subscription_symbol,
            base::market::ExchangeType::Bitget,
            contract_type,
            expiry_timestamp,
            resolve_timestamp) {}

 public:
  static std::unique_ptr<BitgetFuturesProduct>
  FromNativeStrImpl(  // rest: BTCUSDT_UMCBL   BTCUSDT_UMCBL_221231  websocket: BTCUSDT BTCUSDT_D1
      const std::string& native_product,
      const int64_t resolve_timestamp) {
    std::string base_str;
    std::string quote_str;
    std::unique_ptr<BitgetFuturesCurrency> base;
    std::unique_ptr<BitgetFuturesCurrency> quote;
    std::string expiry_str;
    using base::symbology::IsDateStr;

    const static std::unordered_set<std::string> product_type_map = {"UMCBL", "DMCBL", "CMCBL"};
    std::vector<std::string> splitted;
    splitted = Split(native_product, "_");
    bool is_rest_symbol = false;
    if (splitted.size() >= 2) {
      is_rest_symbol = (product_type_map.count(splitted[1]) > 0) ? true : false;
    }

    std::smatch match;
    static const std::regex reg("([A-Z0-9]{2,12})(USD|USDT|PERP)");
    bool res = std::regex_match(splitted[0], match, reg);
    if (!res) {
      return {};
    }
    // BTC USD
    base_str = match[1];
    quote_str = match[2];
    if (match[2] == "PERP") {
      quote_str = "USDC";
    }
    expiry_str = "PERPETUAL";

    if (splitted.size() == 2 && !is_rest_symbol) {
      if (splitted[1] == "D1") {
        expiry_str = "QUARTER";
      } else if (splitted[1] == "D2") {
        expiry_str = "NEXT_QUARTER";
      } else {
        return {};
      }
    }
    if (splitted.size() == 3 && is_rest_symbol) {  // BTCUSD_DMCBL_230331
      expiry_str = std::string("20") + splitted[2];
      if (!IsDateStr(expiry_str)) {
        return {};
      }
    }

    base = BitgetFuturesCurrency::FromNativeStrNoThrow(base_str, resolve_timestamp);
    quote = BitgetFuturesCurrency::FromNativeStrNoThrow(quote_str, resolve_timestamp);

    std::string native_product_tmp;
    std::string native_subscription_symbol;
    ContractType contract_type;
    int64_t expiry_timestamp;
    bool success = CalculateParams(
        *base,
        *quote,
        expiry_str,
        resolve_timestamp,
        &native_product_tmp,
        &native_subscription_symbol,
        &contract_type,
        &expiry_timestamp);
    if (success) {
      if (is_rest_symbol) {
        DCHECK_EQ(native_product_tmp, native_product);
      } else {
        DCHECK_EQ(native_subscription_symbol, native_product);
      }

      return std::make_unique<BitgetFuturesProduct>(
          *base,
          *quote,
          native_product,
          native_subscription_symbol,
          contract_type,
          expiry_timestamp,
          resolve_timestamp);
    } else {
      return {};
    }
  }

  static bool CalculateParams(
      const BitgetFuturesCurrency& base,
      const BitgetFuturesCurrency& quote,
      const std::string& expiry_str,
      const int64_t resolve_timestamp,
      std::string* native_product,
      std::string* native_subscription_symbol,
      ContractType* contract_type,
      int64_t* expiry_timestamp) {
    using base::symbology::ContractType;
    using base::symbology::FindExpiryFriday;
    using base::symbology::IsDateStr;
    using base::symbology::StringToContractType;
    using base::symbology::ToDateStr;
    static const int64_t expiry_time_of_day_nanosec = 8 * 3600 * 1e9;

    if (IsDateStr(expiry_str)) {
      bool found = false;
      for (auto type :
           {ContractType::QUARTER, ContractType::NEXT_QUARTER, ContractType::NNEXT_QUARTER}) {
        auto timestamp = FindExpiryFriday(
            ExchangeType::Bitget,
            type,
            resolve_timestamp,
            expiry_time_of_day_nanosec);
        if (expiry_str == ToDateStr(timestamp) || expiry_str == ToDateStr(timestamp).substr(2)) {
          *contract_type = type;
          *expiry_timestamp = timestamp;
          found = true;
          break;
        }
      }
      if (!found) {
        return false;
      }
    } else {
      auto type = StringToContractType(expiry_str);
      if (type) {
        *contract_type = type.value();
        if (*contract_type == ContractType::PERPETUAL) {
          *expiry_timestamp = FuturesProduct::PERPETUAL_TIMESTAMP;
        } else {
          *expiry_timestamp = FindExpiryFriday(
              ExchangeType::Bitget,
              *contract_type,
              resolve_timestamp,
              expiry_time_of_day_nanosec);
        }
      } else {
        return false;
      }
    }

    auto nos = CalculateOrderNativeSymbol(contract_type, base, quote, *expiry_timestamp);
    auto oss = CalculateSubscriptionNativeSymbol(contract_type, base, quote);
    if (nos.str().size() == 0 || oss.str().size() == 0) {
      return false;
    }
    *native_product = nos.str();
    *native_subscription_symbol = oss.str();
    return true;
  }

  static std::stringstream CalculateOrderNativeSymbol(
      const ContractType* contract_type,
      const BitgetFuturesCurrency& base,
      const BitgetFuturesCurrency& quote,
      int64_t expiry_timestamp) {
    using base::symbology::ToDateStr;
    std::stringstream nos;
    const static std::unordered_map<std::string, std::string> currency_product_map = {
        {"USDT", "UMCBL"},
        {"USD", "DMCBL"},
        {"USDC", "CMCBL"}};
    nos << base.native_currency();
    if (quote.native_currency() == "USDC") {
      nos << "PERP";
    } else {
      nos << quote.native_currency();
    }

    nos << "_" << currency_product_map.at(quote.native_currency());

    if (*contract_type != ContractType::PERPETUAL) {
      nos << "_" << ToDateStr(expiry_timestamp).substr(2);
    }

    return nos;
  }

  static std::stringstream CalculateSubscriptionNativeSymbol(
      const ContractType* contract_type,
      const BitgetFuturesCurrency& base,
      const BitgetFuturesCurrency& quote) {
    std::stringstream oss;
    if (*contract_type == ContractType::PERPETUAL) {
      if (quote.native_currency() == "USDC") {
        oss << base.native_currency() << "PERP";
      } else {
        oss << base.native_currency() << quote.native_currency();
      }
    } else if (*contract_type == ContractType::QUARTER) {
      oss << base.native_currency() << quote.native_currency() << "_D1";
    } else if (
        *contract_type == ContractType::NEXT_QUARTER ||
        *contract_type == ContractType::NNEXT_QUARTER) {
      oss << base.native_currency() << quote.native_currency() << "_D2";
    } else {
      return {};
    }

    return oss;
  }
};
}  // namespace coin2::exchange::bitget_futures::symbology
